﻿using DotNetCommon.Data;
using DotNetCommon.Extensions;
using DotNetCommon.Logger;
using DotNetCommon.Serialize;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace DotNetCommon
{
    /// <summary>
    /// 基于Roslyn的Mapper
    /// </summary>
    public class RoslynMapper
    {
        private static readonly ILogger<RoslynMapper> logger = LoggerFactory.CreateLogger<RoslynMapper>();
        /// <summary>
        /// 当RoslynMapper触发时产生事件
        /// </summary>
        public static event Action<Context, Result<Assembly>> EventRoslynMapperTriggered;
        #region 代码生成
        #region 模型
        /// <summary>
        /// 生成代码上下文
        /// </summary>
        public class Context
        {
            /// <summary>
            /// 命名空间
            /// </summary>
            public List<string> NameSpaces { get; set; }
            /// <summary>
            /// 转换方法
            /// </summary>
            public List<MapContext> Methods { get; set; }

            /// <summary>
            /// 生成的code
            /// </summary>
            public string Code { get; set; }
            /// <summary>
            /// 类名称
            /// </summary>
            public string ClassName { get; set; }
        }

        /// <summary>
        /// 实体映射(表示一个转换方法)
        /// </summary>
        public class MapContext
        {
            /// <summary>
            /// 源type名称(内部类包含外层名称)
            /// </summary>
            public string SrcTypeName { get; set; }
            /// <summary>
            /// 源type
            /// </summary>
            public Type SrcType { get; set; }
            /// <summary>
            /// 目标type名称(内部类包含外层名称)
            /// </summary>
            public string DestTypeName { get; set; }
            /// <summary>
            /// 目标type
            /// </summary>
            public Type DestType { get; set; }

            /// <summary>
            /// 属性映射
            /// </summary>
            public List<PropContext> Props { get; set; }

            /// <summary>
            /// 转换方法名字
            /// </summary>
            public string MethodName { get; set; }

            /// <summary>
            /// 目标类型名称
            /// </summary>
            public string SrcTypeVarName { get; set; }

            /// <summary>
            /// 目标类型名称
            /// </summary>
            public string DestTypeVarName { get; set; }

            /// <summary>
            /// 是否目标是数组
            /// </summary>
            public bool IsToArray { get; set; }

            /// <summary>
            /// 是否目标是List
            /// </summary>
            public bool IsToList { get; set; }

            /// <summary>
            /// 当这个转换是针对集合时引用的其他转换方法
            /// </summary>
            public MapContext InnerMap { get; set; }

            /// <summary>
            /// 包含的转换
            /// </summary>
            public List<(Type fromType, Type destType)> ConvertTypes { get; set; }

            /// <summary>
            /// 是否需要容器缓存,只有当模型本身可能存在循环引用的时候才使用缓存
            /// </summary>
            public bool NeedContainer { get; set; }

            /// <summary>
            /// 是否暴露出去(当子级因为父级而要防循环,但本身并不需要防循环时 为 false)
            /// </summary>
            public bool IsExpose { get; set; } = true;

            /// <summary>
            /// 是否是分裂生成的方法
            /// </summary>
            public bool IsSplit { get; set; } = false;
        }

        /// <summary>
        /// 属性映射(表示实体的一个属性转换)
        /// </summary>
        public class PropContext : MapContext
        {
            /// <summary>
            /// 是否是序列
            /// </summary>
            public bool IsSequence { get; set; }
            /// <summary>
            /// 是否终止遍历
            /// </summary>
            public bool IsEnd { get; set; }

            /// <summary>
            /// 该属性需要的其他转换方法
            /// </summary>
            public MapContext Map { get; set; }

            /// <summary>
            /// 目标属性
            /// </summary>
            public PropertyInfo DestPropInfo { get; set; }
            /// <summary>
            /// 源属性
            /// </summary>
            public PropertyInfo SrcPropInfo { get; set; }
        }
        #endregion
        private Context ctx = new Context();
        private static int Counter = 0;
        /// <summary>
        /// 生成不同类型映射代码
        /// </summary>
        /// <param name="src">源类型</param>
        /// <param name="dest">目标类型</param>
        /// <returns></returns>
        public Context GenerateMapCode(Type src, Type dest)
        {
            if (src == typeof(string) || dest == typeof(string)) throw new ArgumentException($"{nameof(dest)}和{nameof(src)} 都不要使用String!");
            if (src.IsValueType || dest.IsValueType) throw new ArgumentException($"{nameof(dest)}和{nameof(src)} 都不要使用值类型!");
            var b1 = src.IsConvertCollectionsOrArrary();
            var b2 = dest.IsConvertCollectionsOrArrary();
            if (b1 != b2) throw new ArgumentException("转换的源类型和目标类型不能一方为集合,而另一方不是!");

            ctx.NameSpaces = new List<string>()
            {
                "DotNetCommon.Extensions",
                "System.Linq",
                "System",
                "System.IO",
                "System.Collections",
                "System.Collections.Generic",
                "Newtonsoft.Json",
                "DotNetCommon.Serialize"
            };

            if (src.IsConvertCollectionsOrArrary() && dest.IsConvertCollectionsOrArrary())
            {
                Type srcFType = src.IsArray ? src.GetElementType() : src.GenericTypeArguments[0];
                Type destFType = dest.IsArray ? dest.GetElementType() : dest.GenericTypeArguments[0];
                return GenerateMapCode(srcFType, destFType);
            }

            ctx.Methods = new List<MapContext>();
            //遍历结构
            Visit(src, dest);
            //处理NeedContainer
            DealNeedCache();

            //生成代码
            var sb = new StringBuilder();
            var builder = new CodeStringBuilder(sb);
            builder.AppendLine("namespace DotNetCommon.MapperGenerator");
            builder.AppendLine("{");
            builder.SetIdent(1);
            ctx.NameSpaces.ForEach(s => builder.AppendLine($"using {s};"));
            var clsName = $"MapperClass{Counter}";
            var clsFullName = $"DotNetCommon.MapperGenerator.{clsName}";
            ctx.ClassName = clsFullName;
            builder.AppendLine($"public static class {clsName}");
            builder.AppendLine($"{{");
            builder.SetIdent(2);
            //生成变量
            var listTypeNames = new List<string>();
            for (int i = 0; i < ctx.Methods.Count; i++)
            {
                var method = ctx.Methods[i];
                if (method.IsToArray || method.IsToList) continue;
                //分裂出来的不需要
                if (method.DestTypeVarName.StartsWith("typesplit")) continue;
                if (!listTypeNames.Contains(method.SrcTypeVarName))
                {
                    builder.AppendLine($"private static Type {method.SrcTypeVarName} = typeof({method.SrcTypeName});");
                    listTypeNames.Add(method.SrcTypeVarName);
                }
                if (!listTypeNames.Contains(method.DestTypeVarName))
                {
                    builder.AppendLine($"private static Type {method.DestTypeVarName} = typeof({method.DestTypeName});");
                    listTypeNames.Add(method.DestTypeName);
                }
            }
            builder.AppendLine("");

            for (int i = 0; i < ctx.Methods.Count; i++)
            {
                var method = ctx.Methods[i];
                #region 添加描述,便于调试
                var desc = $"//{method.SrcTypeName} => {method.DestTypeName}";
                builder.AppendLine(desc);
                var srcShort = method.SrcTypeName;
                var destShort = method.DestTypeName;
                if (method.IsToArray || method.IsToList)
                {
                    var tmp = method.SrcTypeName.SplitAndTrimTo<string>(".").LastOrDefault();
                    var tmp2 = method.DestTypeName.SplitAndTrimTo<string>(".").LastOrDefault();
                    if (tmp.Contains("<"))
                    {
                        srcShort = "IEnumerable<" + tmp + ">";
                    }
                    else
                    {
                        srcShort = "IEnumerable<" + tmp.Replace(">", "") + ">";
                    }
                    if (method.IsToList)
                    {
                        if (tmp2.Contains("<"))
                        {
                            destShort = "List<" + tmp2 + ">";
                        }
                        else
                        {
                            destShort = "List<" + tmp2.Replace(">", "") + ">";
                        }
                    }
                    else
                    {
                        destShort = tmp2;
                    }
                }
                else
                {
                    srcShort = method.SrcTypeName.SplitAndTrimTo<string>(".").LastOrDefault();
                    destShort = method.DestTypeName.SplitAndTrimTo<string>(".").LastOrDefault();
                }
                desc = $"//{srcShort} => {destShort} {(method.NeedContainer ? "防循环引用" : "不用防")}";
                builder.AppendLine(desc);
                #endregion
                builder.AppendLine($"{(method.IsExpose ? "public" : "private")} static object {method.MethodName}(object srcObj, Dictionary<(object src, Type srcType, Type destType), object> container = null)");
                builder.AppendLine($"{{");
                builder.SetIdent(3);
                builder.AppendLine("if (srcObj == null) return null;");
                builder.AppendLine($"var src = srcObj as {method.SrcTypeName};");
                if (method.NeedContainer)
                {
                    //大部分情况下不需要缓存容器,提升性能
                    builder.AppendLine("if (container == null) container = new Dictionary<(object src, Type srcType, Type destType), object>();");
                    builder.AppendLine($"var key = (src,{method.SrcTypeVarName},{method.DestTypeVarName});");
                    builder.AppendLine($"if(container.ContainsKey(key)) return ({method.DestTypeName})container[key];");
                }
                if (method.IsToArray)
                {
                    builder.AppendLine($"var res = src.Select(i => ({method.InnerMap.DestTypeName}){method.InnerMap.MethodName}(i,container)).ToArray();");
                }
                else if (method.IsToList)
                {
                    builder.AppendLine($"var res = src.Select(i => ({method.InnerMap.DestTypeName}){method.InnerMap.MethodName}(i,container)).ToList();");
                }
                else
                {
                    builder.AppendLine($"var res = new {method.DestTypeName}();");
                    if (method.NeedContainer)
                    {
                        //大部分情况下不需要缓存容器,提升性能
                        builder.AppendLine($"container.Add(key,res);");
                    }
                    for (var j = 0; j < method.Props.Count; j++)
                    {
                        var prop = method.Props[j];
                        if (prop.IsEnd)
                        {
                            #region 直接转换
                            if (prop.IsSequence)
                            {
                                #region 集合
                                builder.AppendLine($"if(src.{prop.DestPropInfo.Name} == null)");
                                builder.AppendLine($"{{");
                                builder.SetIdent(4);
                                builder.AppendLine($"res.{prop.DestPropInfo.Name}=null;");
                                builder.SetIdent(3);
                                builder.AppendLine($"}}");
                                builder.AppendLine($"else");
                                builder.AppendLine($"{{");
                                builder.SetIdent(4);
                                var srcFType = prop.SrcType.IsArray ? prop.SrcType.GetElementType() : prop.SrcType.GenericTypeArguments[0];
                                var destFType = prop.DestType.IsArray ? prop.DestType.GetElementType() : prop.DestType.GenericTypeArguments[0];
                                //处理内部不一致
                                var select = "";
                                if (destFType != srcFType)
                                {
                                    if (destFType.IsAssignableFrom(srcFType))
                                    {
                                        select = $".Select(i => ({destFType.GetClassFullName()})i)";
                                    }
                                    else
                                    {
                                        if (!destFType.IsNullable() && srcFType.IsNullable())
                                        {
                                            // int? -> int
                                            var srcType2 = srcFType.GenericTypeArguments[0];
                                            if (srcType2 == destFType)
                                            {
                                                select = $".Select(i => i?? default({destFType.GetClassFullName()}))";
                                            }
                                            else
                                            {
                                                select = $".Select(i => i == null ? default({destFType.GetClassFullName()}) : ({destFType.GetClassFullName()})i.To(typeof({destFType.GetClassFullName()})))";
                                            }
                                        }
                                        else if (!destFType.IsNullable() && !srcFType.IsValueType)
                                        {
                                            select = $".Select(i => i == null ? default({destFType.GetClassFullName()}) : ({destFType.GetClassFullName()})i.To(typeof({destFType.GetClassFullName()})))";
                                        }
                                        else if (destFType == typeof(string))
                                        {
                                            if (!srcFType.IsNullable() && srcFType.IsValueType) select = $".Select(i => i.ToString())";
                                            else select = $".Select(i => i?.ToString())";
                                        }
                                        else
                                        {
                                            select = $".Select(i => ({destFType.GetClassFullName()})i.To(typeof({destFType.GetClassFullName()})))";
                                        }
                                    }
                                }

                                if (prop.DestType.IsArray)
                                {
                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}{select}.ToArray();");
                                }
                                else
                                {
                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}{select}.ToList();");
                                }
                                builder.SetIdent(3);
                                builder.AppendLine($"}}");
                                #endregion
                            }
                            else
                            {
                                #region 非集合
                                if (prop.DestType.IsAssignableFrom(prop.SrcType))
                                {
                                    //可以直接转
                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name};");
                                    continue;
                                }

                                if (!prop.DestType.IsNullable() && prop.SrcType.IsNullable())
                                {
                                    // int? -> int
                                    var srcType = prop.SrcType.GenericTypeArguments[0];
                                    if (srcType == prop.DestType)
                                    {
                                        builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name} ?? default({prop.DestTypeName});");
                                        continue;
                                    }
                                    else
                                    {
                                        builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name} == null ? default({prop.DestTypeName}) :  ({prop.DestTypeName})src.{prop.SrcPropInfo.Name}.To(typeof({prop.DestTypeName}));");
                                        continue;
                                    }
                                }
                                else if (!prop.DestType.IsNullable() && !prop.SrcType.IsValueType)
                                {
                                    // string => int
                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name} == null ? default({prop.DestTypeName}) :  ({prop.DestTypeName})src.{prop.SrcPropInfo.Name}.To(typeof({prop.DestTypeName}));");
                                    continue;
                                }
                                else if (prop.DestType == typeof(string))
                                {
                                    #region 其他类型转string
                                    // other => string
                                    if (prop.SrcType == typeof(DateTime) || prop.SrcType == typeof(DateTime?)
                                        || prop.SrcType == typeof(DateTimeOffset) || prop.SrcType == typeof(DateTimeOffset?)
                                        || prop.SrcType == typeof(Guid) || prop.SrcType == typeof(Guid?))
                                    {
                                        //日期或guid转字符串
                                        var converter = prop.SrcPropInfo
                                            .GetCustomAttributes()
                                            .FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        if (converter == null)
                                        {
                                            converter = prop.DestPropInfo
                                            .GetCustomAttributes()
                                            .FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        }
                                        if (converter != null && converter.Args?.Length > 0)
                                        {
                                            var formatter = converter.Args.FirstOrDefault().ToString();
                                            if (formatter.IsNotNullOrEmptyOrWhiteSpace())
                                            {
                                                builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}{(prop.SrcType.IsNullable() ? "?" : "")}.ToString(\"{formatter}\");");
                                                continue;
                                            }
                                        }
                                    }
                                    //其他类型 -> string
                                    if (!prop.SrcType.IsValueType || prop.SrcType.IsNullable())
                                    {
                                        //Object -> string 或 int? -> string
                                        builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}?.ToString();");
                                    }
                                    else
                                    {
                                        //值类型且不可空
                                        builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}.ToString();");
                                    }
                                    continue;
                                    #endregion
                                }
                                if (prop.DestType == typeof(DateTime) || prop.DestType == typeof(DateTime?)
                                    || prop.DestType == typeof(DateTimeOffset) || prop.DestType == typeof(DateTimeOffset?)
                                    || prop.DestType == typeof(Guid) || prop.DestType == typeof(Guid?))
                                {
                                    if (prop.SrcType == typeof(string))
                                    {
                                        #region 字符串转日期或guid
                                        var typeName = prop.DestType.GetClassFullName();
                                        var converter = prop.SrcPropInfo
                                            .GetCustomAttributes()
                                            .FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        if (converter == null)
                                        {
                                            converter = prop.DestPropInfo
                                            .GetCustomAttributes()
                                            .FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        }
                                        var type = "";
                                        if (prop.DestType == typeof(Guid) || prop.DestType == typeof(Guid?)) type = "Guid";
                                        else if (prop.DestType == typeof(DateTime) || prop.DestType == typeof(DateTime?)) type = "DateTime";
                                        else type = "DateTimeOffSet";
                                        if (converter != null && converter.Args?.Length > 0)
                                        {
                                            var formatter = converter.Args.FirstOrDefault().ToString();
                                            if (formatter.IsNotNullOrEmptyOrWhiteSpace())
                                            {
                                                if (prop.DestType.IsNullable())
                                                {
                                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name} == null?null:({typeName})src.{prop.SrcPropInfo.Name}.To(typeof({typeName}), \"{formatter}\");");
                                                }
                                                else
                                                {
                                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}.IsNullOrEmptyOrWhiteSpace()?default({type}):({typeName})src.{prop.SrcPropInfo.Name}.To(typeof({typeName}), \"{formatter}\");");
                                                }
                                                continue;
                                            }
                                        }
                                        var nulldefault = "null";
                                        if (!prop.DestType.IsNullable()) nulldefault = $"default({type})";
                                        builder.AppendLine($"res.{prop.DestPropInfo.Name} =  src.{prop.SrcPropInfo.Name}.IsNullOrEmptyOrWhiteSpace()?{nulldefault}:({typeName})src.{prop.SrcPropInfo.Name}.Parse(src.{prop.DestPropInfo.Name});");
                                        continue;
                                        #endregion
                                    }
                                }
                                //其他类型 -> 其他类型
                                builder.AppendLine($"res.{prop.DestPropInfo.Name} = ({prop.DestTypeName})src.{prop.SrcPropInfo.Name}.To(typeof({prop.DestTypeName}));");
                                #endregion
                            }
                            #endregion
                        }
                        else
                        {
                            #region 调用其他转换
                            if (prop.IsSequence)
                            {
                                //集合
                                builder.AppendLine($"if(src.{prop.DestPropInfo.Name} == null)");
                                builder.AppendLine($"{{");
                                builder.SetIdent(4);
                                builder.AppendLine($"res.{prop.DestPropInfo.Name}=null;");
                                builder.SetIdent(3);
                                builder.AppendLine($"}}");
                                builder.AppendLine($"else");
                                builder.AppendLine($"{{");
                                builder.SetIdent(4);
                                if (prop.DestType.IsArray)
                                {
                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}.Select(i=>{prop.Map.MethodName}(i, container) as {prop.Map.DestTypeName}).ToArray();");
                                }
                                else
                                {
                                    builder.AppendLine($"res.{prop.DestPropInfo.Name} = src.{prop.SrcPropInfo.Name}.Select(i=>{prop.Map.MethodName}(i, container) as {prop.Map.DestTypeName}).ToList();");
                                }
                                builder.SetIdent(3);
                                builder.AppendLine($"}}");
                            }
                            else
                            {
                                builder.AppendLine($"res.{prop.DestPropInfo.Name} = ({prop.Map.DestTypeName}){prop.Map.MethodName}(src.{prop.SrcPropInfo.Name}, container);");
                            }
                            #endregion
                        }
                    }
                }
                builder.AppendLine("return res;");
                builder.SetIdent(2);
                builder.AppendLine("}");
                builder.AppendLine("");
            }
            builder.SetIdent(1);
            builder.AppendLine("}");
            builder.SetIdent(0);
            builder.AppendLine("}");
            ctx.Code = builder.ToString();
            return ctx;
        }

        private void DealNeedCache()
        {
            var methods = ctx.Methods.Where(i => i.IsToArray == false && i.IsToList == false).ToList();
            //先判断哪些需要防循环,哪些不需要

            var cacheTypes = new Dictionary<MapContext, List<(Type fromType, Type destType)>>();
            foreach (var method in methods)
            {
                var types = new List<(Type fromType, Type destType)>() { (method.SrcType, method.DestType) };
                recursion0(method, new List<MapContext>(), types);
                cacheTypes.Add(method, types);
            }
            foreach (var method in methods)
            {
                method.ConvertTypes = cacheTypes[method];
            }
            void recursion0(MapContext mapContext, List<MapContext> container, List<(Type fromType, Type destType)> convertTypes)
            {
                if (container.Contains(mapContext)) return;
                container.Add(mapContext);
                convertTypes.AddRange(mapContext.ConvertTypes ?? new List<(Type fromType, Type destType)>());
                var props = mapContext.Props.Where(i => !i.IsEnd).ToList();
                foreach (var prop in props)
                {
                    recursion0(prop.Map, container, convertTypes);
                }
            }
            //当出现重复的时候设置NeedContainer=true
            methods.ForEach(m => m.NeedContainer = m.ConvertTypes.GroupBy(i => i).Any(i => i.Count() > 1));

            //因为子级要防循环引用,所以父级肯定也要防
            //第一次遍历,找出因为子级需要容器,牵连父级也必须要容器
            var needs = methods.Where(i => i.NeedContainer).ToList();
            var noNeeds = methods.Where(i => i.NeedContainer == false).ToList();
            List<MapContext> newNeeds = null;
            do
            {
                List<MapContext> tmp = null;
                if (newNeeds == null)
                {
                    //第一次
                    tmp = needs;
                }
                else
                {
                    tmp = newNeeds;
                }
                recursion(tmp);
            } while (noNeeds.Count > 0 && newNeeds.Count > 0);


            //已知needs是需要容器的, 从noNeeds查找出来引用了它们的,那么查找出来的肯定也需要容器
            void recursion(List<MapContext> mapContext)
            {
                var refs = noNeeds.Where(i => i.Props.Any(p => mapContext.Contains(p.Map))).ToList();
                refs.ForEach(i => i.NeedContainer = true);
                newNeeds = new List<MapContext>();
                newNeeds.AddRange(refs);
                refs.ForEach(i => noNeeds.Remove(i));
            }

            //第二次遍历,找出父级需要容器,但自身不需要容器的
            methods = ctx.Methods.Where(i => i.IsToArray == false && i.IsToList == false).ToList();
            needs = methods.Where(i => i.NeedContainer).ToList();

            //需要分裂的(只包含顶级)
            var alreadyVisit = new List<MapContext>();
            noNeeds = new List<MapContext>();
            needs.ForEach(i => recursion2(i));

            //第三次, 进行分裂处理
            noNeeds.ForEach(i =>
            {
                var map = recursion3(i);
                ctx.Methods.Add(map);
            });

            void recursion2(MapContext mapContext)
            {
                if (alreadyVisit.Contains(mapContext)) return;
                alreadyVisit.Add(mapContext);
                var props = mapContext.Props.Where(i => !i.IsEnd);
                foreach (var prop in props)
                {
                    if (prop.Map.NeedContainer)
                    {
                        recursion2(prop.Map);
                        continue;
                    }
                    recursion22(prop.Map);
                };
            }
            void recursion22(MapContext mapContext)
            {
                if (!noNeeds.Contains(mapContext))
                {
                    noNeeds.Add(mapContext);
                    mapContext.Props.Where(i => !i.IsEnd).ForEach(i => recursion22(i.Map));
                }
            }
            MapContext recursion3(MapContext mapContext)
            {
                //将原来的改成需要容器且不暴露
                mapContext.IsExpose = false;
                mapContext.NeedContainer = true;
                //分裂一个新的
                var counter = Interlocked.Increment(ref Counter);
                var methodName = $"Map{counter}";
                var newMap = new MapContext
                {
                    DestType = mapContext.DestType,
                    DestTypeName = mapContext.DestTypeName,
                    SrcTypeVarName = $"typesplit{counter}_0",
                    DestTypeVarName = $"typesplit{counter}_1",
                    InnerMap = mapContext.InnerMap,
                    IsExpose = true,
                    IsToArray = mapContext.IsToArray,
                    IsToList = mapContext.IsToList,
                    MethodName = methodName,
                    NeedContainer = false,
                    Props = new List<PropContext>(),//先置为空
                    SrcType = mapContext.SrcType,
                    SrcTypeName = mapContext.SrcTypeName,
                    IsSplit = true
                };
                mapContext.Props.ForEach(p =>
                {
                    if (p.IsEnd)
                    {
                        newMap.Props.Add(p);
                        return;
                    }
                    var prop = new PropContext
                    {
                        DestPropInfo = p.DestPropInfo,
                        IsToArray = p.IsToArray,
                        Props = p.Props,
                        DestType = p.DestType,
                        DestTypeName = p.DestTypeName,
                        DestTypeVarName = p.DestTypeName,
                        InnerMap = p.InnerMap,
                        IsEnd = p.IsEnd,
                        IsExpose = p.IsExpose,
                        IsSequence = p.IsSequence,
                        IsToList = p.IsToList,
                        Map = null,
                        MethodName = p.MethodName,
                        NeedContainer = p.NeedContainer,
                        SrcPropInfo = p.SrcPropInfo,
                        SrcType = p.SrcType,
                        SrcTypeName = p.SrcTypeName
                    };
                    var map = ctx.Methods.FirstOrDefault(i => i.SrcType == p.Map.SrcType && i.DestType == p.Map.DestType && i.NeedContainer == false);
                    if (map != null)
                    {
                        prop.Map = map;
                    }
                    else
                    {
                        prop.Map = recursion3(p.Map);
                    }
                    newMap.Props.Add(prop);
                });
                return newMap;
            }
        }

        private MapContext Visit(Type src, Type dest)
        {
            var srcNamespace = src.Namespace;
            var destNamespace = dest.Namespace;
            var counter = Interlocked.Increment(ref Counter);
            var methodName = $"Map{counter}";
            var mapCtx = new MapContext()
            {
                SrcType = src,
                SrcTypeName = src.GetClassFullName(),
                DestType = dest,
                DestTypeName = dest.GetClassFullName(),
                Props = new List<PropContext>(),
                MethodName = methodName,
                SrcTypeVarName = $"type{counter}_0",
                DestTypeVarName = $"type{counter}_1",
                IsToArray = false,
                IsToList = false,
                ConvertTypes = new List<(Type fromType, Type destType)>()
            };
            ctx.Methods.Add(mapCtx);
            #region 增加集合转换支持
            //增加集合转换支持
            //ToArray
            var tmpSrcType = typeof(IEnumerable<>).MakeGenericType(src);
            var tmpDestType = dest.MakeArrayType();
            var extMap = new MapContext()
            {
                SrcType = tmpSrcType,
                SrcTypeName = tmpSrcType.GetClassFullName(),
                DestType = tmpDestType,
                DestTypeName = tmpDestType.GetClassFullName(),
                Props = new List<PropContext>(),
                MethodName = $"Map2Array{counter}",
                SrcTypeVarName = $"typeArray{counter}_0",
                DestTypeVarName = $"typeArray{counter}_1",
                InnerMap = mapCtx,
                IsToArray = true,
                IsToList = false,
            };
            ctx.Methods.Add(extMap);
            //ToList 
            tmpDestType = typeof(List<>).MakeGenericType(src);
            extMap = new MapContext()
            {
                SrcType = tmpSrcType,
                SrcTypeName = tmpSrcType.GetClassFullName(),
                DestType = tmpDestType,
                DestTypeName = tmpDestType.GetClassFullName(),
                Props = new List<PropContext>(),
                MethodName = $"Map2List{counter}",
                SrcTypeVarName = $"typeList{counter}_0",
                DestTypeVarName = $"typeList{counter}_1",
                InnerMap = mapCtx,
                IsToArray = false,
                IsToList = true,
            };
            ctx.Methods.Add(extMap);
            #endregion
            var srcProps = src.GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
            var destProps = dest.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(i => i.CanWrite).ToList();
            for (var i = 0; i < destProps.Count; i++)
            {
                var destProp = destProps[i];
                var srcProp = srcProps.FirstOrDefault(i => i.Name == destProp.Name);
                if (srcProp == null) continue;
                var srcType = srcProp.PropertyType;
                var destType = destProp.PropertyType;
                var propContxt = new PropContext
                {
                    DestPropInfo = destProp,
                    SrcPropInfo = srcProp,
                    DestType = destProp.PropertyType,
                    SrcType = srcProp.PropertyType,
                    SrcTypeName = srcProp.PropertyType.GetClassFullName(),
                    DestTypeName = destProp.PropertyType.GetClassFullName()
                };
                mapCtx.Props.Add(propContxt);
                //简单类型终止
                if (srcType.IsSimpleType() || destType.IsSimpleType())
                {
                    propContxt.IsEnd = true;
                    continue;
                }
                //可以兼容的终止
                if (destType == srcType)
                {
                    propContxt.IsEnd = true;
                    continue;
                }

                if (srcType.IsConvertCollectionsOrArrary() && destType.IsConvertCollectionsOrArrary())
                {
                    //集合或数组 它们之间可以互转
                    propContxt.IsSequence = true;
                    Type srcFType = srcType.IsConvertCollections() ? srcType.GenericTypeArguments[0] : srcType.GetElementType();
                    Type destFType = destType.IsConvertCollections() ? destType.GenericTypeArguments[0] : destType.GetElementType();
                    if (destFType.IsSimpleType() || srcFType.IsSimpleType())
                    {
                        propContxt.IsEnd = true;
                    }
                    else
                    {
                        var tmp2 = ctx.Methods.Find(ctx => ctx.SrcType == srcFType && ctx.DestType == destFType);
                        mapCtx.ConvertTypes.Add((srcFType, destFType));
                        if (tmp2 != null)
                        {
                            //已经有转换方法了
                            propContxt.Map = tmp2;
                        }
                        else
                        {
                            //新生成转换
                            propContxt.Map = Visit(srcFType, destFType);
                        }
                    }
                }
                else
                {
                    //都不是集合或数组
                    var tmp = ctx.Methods.Find(ctx => ctx.SrcType == srcType && ctx.DestType == destType);
                    mapCtx.ConvertTypes.Add((srcType, destType));
                    if (tmp != null)
                    {
                        //已经有转换方法了
                        propContxt.Map = tmp;
                    }
                    else
                    {
                        //新生成转换
                        propContxt.Map = Visit(srcType, destType);
                    }
                }
            }
            return mapCtx;
        }

        internal static List<string> CollectNames = new List<string>()
        {
            "System.Collections.Generic.List`1",
            "System.Collections.Generic.IList`1",
            "System.Collections.Generic.IEnumerable`1",
            "System.Collections.Generic.ICollection`1"
        };

        private class CodeStringBuilder
        {
            private readonly StringBuilder sb;

            public CodeStringBuilder(StringBuilder sb)
            {
                this.sb = sb;
            }

            private string indent = "";
            public void SetIdent(int indent = 0)
            {
                this.indent = " ".Repeat(indent * 4);
            }

            public void AppendLine(string code)
            {
                if (code.IsNullOrEmptyOrWhiteSpace()) sb.AppendLine();
                else sb.AppendLine($"{indent}{code}");
            }

            public override string ToString()
            {
                return sb.ToString();
            }
        }
        #endregion

        #region 处理消息

        /// <summary>
        /// 预注册模型转换,如:
        /// <list type="number">
        /// <item>RegisterConvert&lt;Person,PersonDto>();</item>
        /// <item>RegisterConvert&lt;PersonDto,Person>();</item>
        /// </list>
        /// </summary>
        /// <typeparam name="TSrc">源类型</typeparam>
        /// <typeparam name="TDest">目标类型</typeparam>
        /// <remarks>注意：不需要注册集合间转换,程序会自动处理，如下面代码不需要:<br /> 
        /// <code>RegisterConvert&lt;List&lt;Peson>,List&lt;PersonDto>>();
        /// </code>
        /// </remarks>
        public static void RegisterConvert<TSrc, TDest>() where TSrc : class where TDest : class
        {
            RegisterConvert(typeof(TSrc), typeof(TDest));
        }

        /// <summary>
        /// 预注册模型转换,如:
        /// <list type="number">
        /// <item>RegisterConvert(typeof(Person),typeof(PersonDto));</item>
        /// <item>RegisterConvert(typeof(PersonDto),typeof(Person));</item>
        /// </list>
        /// </summary>
        /// <param name="srcType">源类型</param>
        /// <param name="destType">目标类型</param>
        /// <remarks>注意：不需要注册集合间转换,程序会自动处理，如下面代码不需要:<br /> 
        /// <code>RegisterConvert(typeof(List&lt;Person>),typeof(List&lt;Person>));</code>
        /// </remarks>
        public static void RegisterConvert(Type srcType, Type destType)
        {
            if (srcType == typeof(string) || destType == typeof(string)) throw new ArgumentException($"{nameof(destType)}和{nameof(srcType)} 都不要使用String!");
            if (srcType.IsValueType || destType.IsValueType) throw new ArgumentException($"{nameof(destType)}和{nameof(srcType)} 都不要使用值类型!");
            var b1 = srcType.IsConvertCollectionsOrArrary();
            var b2 = destType.IsConvertCollectionsOrArrary();
            if (b1 != b2) throw new ArgumentException("转换的源类型和目标类型不能一方为集合,而另一方不是!");

            initQueue();
            mapperQueue.TryAdd((srcType, destType));
        }

        private static ConcurrentDictionary<(Type srcType, Type destType), Result<Func<object, Dictionary<(object src, Type destType), object>, object>>> cacheFuncs =
            new ConcurrentDictionary<(Type srcType, Type destType), Result<Func<object, Dictionary<(object src, Type destType), object>, object>>>();

        private static BlockingCollection<(Type srcType, Type destType)> mapperQueue = new BlockingCollection<(Type srcType, Type destType)>();
        private static bool isInitQueue = false;

        private static void initQueue()
        {
            if (isInitQueue) return;
            lock (typeof(RoslynMapper))
            {
                if (isInitQueue) return;
                isInitQueue = true;
                Task.Factory.StartNew(() =>
                {
                    while (true)
                    {
                        var key = mapperQueue.Take();
                        try
                        {
                            if (cacheFuncs.ContainsKey(key)) continue;
                            var ctx = new RoslynMapper().GenerateMapCode(key.srcType, key.destType);
                            var result = RoslynHelper.GenerateAssemblyFromCode(ctx.Code);
                            RoslynMapper.EventRoslynMapperTriggered?.Invoke(ctx, result);
                            if (!result.Success)
                            {
                                //编译失败了也要放进去,防止重复编译
                                cacheFuncs.TryAdd(key, Result.NotOk(result.Message));
                                logger.LogError($"Roslyn编译Mapper队列消息(失败): {key.srcType.GetClassFullName()} => {key.destType.GetClassFullName()},错误如下:\r\n{result.Message}");
                                continue;
                            }
                            else
                            {
                                logger.LogInformation($"Roslyn编译Mapper队列消息(成功): {key.srcType.GetClassFullName()} => {key.destType.GetClassFullName()},共生成: {ctx.Methods.Count} 个方法, 对外暴露: {ctx.Methods.Count(i => i.IsExpose)} 个方法, 共 {ctx.Methods.Count(i => i.IsToArray == false && i.IsToList == false)} 个类之间的转换, 自动生成了: {ctx.Methods.Count(i => i.IsToList || i.IsToArray)} 个集合/数组方法,其中分裂出了: {ctx.Methods.Count(i => i.IsSplit)} 个方法.");
                            }
                            var geneType = result.Data.GetType(ctx.ClassName);
                            for (var i = 0; i < ctx.Methods.Count; i++)
                            {
                                var method = ctx.Methods[i];

                                //非暴露的略过
                                if (!method.IsExpose) continue;
                                key = (method.SrcType, method.DestType);
                                //已有的略过
                                if (cacheFuncs.ContainsKey(key)) continue;

                                var methodInfo = geneType.GetMethod(method.MethodName);

                                //IL生成
                                DynamicMethod md = new DynamicMethod("commonmapper", typeof(object), new Type[] { typeof(object), typeof(Dictionary<(object src, Type destType), object>) }, typeof(RoslynMapper).Module);
                                ILGenerator il = md.GetILGenerator();

                                il.Emit(OpCodes.Ldarg_0);
                                il.Emit(OpCodes.Ldarg_1);
                                il.Emit(OpCodes.Call, methodInfo);
                                il.Emit(OpCodes.Ret);

                                var func = (Func<object, Dictionary<(object src, Type destType), object>, object>)md.CreateDelegate(typeof(Func<object, Dictionary<(object src, Type destType), object>, object>));

                                if (method.IsToArray || method.IsToList)
                                {
                                    Type destArrayType = method.IsToArray ? method.InnerMap.DestType.MakeArrayType() : typeof(List<>).MakeGenericType(method.InnerMap.DestType);

                                    //IEnumerable<Person> => PersonDto[]/List<PersonDto>
                                    var srcType2 = typeof(IEnumerable<>).MakeGenericType(method.InnerMap.SrcType);
                                    key = (srcType2, destArrayType);
                                    cacheFuncs.TryAdd(key, Result.Ok(func));

                                    //ICollection<Person> => PersonDto[]/List<PersonDto>
                                    srcType2 = typeof(ICollection<>).MakeGenericType(method.InnerMap.SrcType);
                                    key = (srcType2, destArrayType);
                                    cacheFuncs.TryAdd(key, Result.Ok(func));

                                    //IList<Person> => PersonDto[]/List<PersonDto>
                                    srcType2 = typeof(IList<>).MakeGenericType(method.InnerMap.SrcType);
                                    key = (srcType2, destArrayType);
                                    cacheFuncs.TryAdd(key, Result.Ok(func));

                                    //List<Person> => PersonDto[]/List<PersonDto>
                                    srcType2 = typeof(List<>).MakeGenericType(method.InnerMap.SrcType);
                                    key = (srcType2, destArrayType);
                                    cacheFuncs.TryAdd(key, Result.Ok(func));
                                }
                                else
                                {
                                    cacheFuncs.TryAdd(key, Result.Ok(func));
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            cacheFuncs.TryAdd(key, Result.NotOk($"自编译失败: {ex.Message} {ex.StackTrace}"));
                            logger.LogError($"自编译失败: {key.srcType.GetClassFullName()} => {key.destType.GetClassFullName()},错误如下:\r\n{ex?.Message}\r\n{ex?.StackTrace}");
                        }
                    }
                }, TaskCreationOptions.LongRunning);
            }
        }

        /// <summary>
        /// 尝试使用Roslyn进行Mapper映射
        /// </summary>
        /// <param name="src"></param>
        /// <param name="destType"></param>
        /// <returns></returns>
        internal static Result<object> TryMapper(object src, Type destType)
        {
            var srcType = src.GetType();
            return TryMapper(src, srcType, destType);

        }

        /// <summary>
        /// 尝试使用Roslyn进行Mapper映射
        /// </summary>
        /// <param name="src"></param>
        /// <param name="srcType"></param>
        /// <param name="destType"></param>
        /// <returns></returns>
        internal static Result<object> TryMapper(object src, Type srcType, Type destType)
        {
            var key = (srcType, destType);
            if (cacheFuncs.TryGetValue(key, out var result))
            {
                if (result.Success)
                {
                    var res = result.Data(src, null);
                    return Result.Ok(res);
                }
            }
            initQueue();
            mapperQueue.TryAdd(key);
            return Result.NotOk("缓存中没有转换方法!");
        }
        #endregion
    }

    internal static class TypeExtensions
    {
        public static bool IsConvertCollectionsOrArrary(this Type type)
        {
            return RoslynMapper.CollectNames.Any(i => type.FullName.StartsWith(i)) || type.IsArray;
        }
        public static bool IsConvertCollections(this Type type)
        {
            return RoslynMapper.CollectNames.Any(i => type.FullName.StartsWith(i));
        }
        public static bool IsConvertArray(this Type type)
        {
            return type.IsArray;
        }
    }
}
